package self.vector.ding.dao.mybatis.generator.plugin;

import static org.mybatis.generator.internal.util.messages.Messages.getString;

import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.Element;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.internal.util.StringUtility;

import self.vector.ding.dao.util.UnderlineToCamelUtil;

public class SelectByColumnAndExamplePlugin extends PluginAdapter {

	public boolean validate(List<String> warnings) {
		return true;
	}

	/**
	 * 指定列的列名
	 */
	private String columnName;
	
	/**
	 * 对应制定列在接口中的参数名称
	 */
	private String parameterName;
	
	private String methodName = "selectBy%sAndExample";
	
	/* (non-Javadoc)
	 * @see org.mybatis.generator.api.PluginAdapter#initialized(org.mybatis.generator.api.IntrospectedTable)
	 */
	@Override
	public void initialized(IntrospectedTable introspectedTable) {
		System.out.println(introspectedTable.getContext().getProperty("columnName"));
		columnName = getProperties().getProperty("columnName");
		parameterName = getProperties().getProperty("parameterName");
		if (!StringUtility.stringHasValue(columnName)) {
			throw new RuntimeException("！！！请配置指定为查询条件的列名！！！");
		}
		if (!StringUtility.stringHasValue(parameterName)) {
			parameterName = UnderlineToCamelUtil.underlineToCamel2(columnName);
		}
		methodName = String.format(methodName, Character.toUpperCase(parameterName.charAt(0)) + parameterName.substring(1));
	}

	/* (non-Javadoc)
	 * @see org.mybatis.generator.api.PluginAdapter#clientGenerated(org.mybatis.generator.api.dom.java.Interface, org.mybatis.generator.api.dom.java.TopLevelClass, org.mybatis.generator.api.IntrospectedTable)
	 */
	public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
			IntrospectedTable introspectedTable) {
		if (!containColumn(introspectedTable)) {
			return true;
		}
		generateMethod(interfaze, topLevelClass, introspectedTable, false);
		if (introspectedTable.getRules().generateResultMapWithBLOBs()) {
			generateMethod(interfaze, topLevelClass, introspectedTable, true);
		}
		return true;
	}

	private boolean containColumn(IntrospectedTable introspectedTable) {
		List<IntrospectedColumn> allColumns = introspectedTable.getAllColumns();
		if (allColumns == null || allColumns.isEmpty()) {
			return false;
		}
		for (IntrospectedColumn column: allColumns) {
			if (columnName.equalsIgnoreCase(column.getActualColumnName())) {
				return true;
			}
		}
		return false;
	}

	public void generateMethod(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable,
			boolean withBlobs) {
		Set<FullyQualifiedJavaType> importedTypes = new TreeSet<FullyQualifiedJavaType>();
		FullyQualifiedJavaType exampleType = new FullyQualifiedJavaType(introspectedTable.getExampleType());
		importedTypes.add(exampleType);
		importedTypes.add(FullyQualifiedJavaType.getObjectInstance());
		importedTypes.add(FullyQualifiedJavaType.getNewListInstance());

		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);

		FullyQualifiedJavaType returnType = FullyQualifiedJavaType.getNewListInstance();

		FullyQualifiedJavaType listType = null;

		if (withBlobs) {
			method.setName(methodName + "WithBLOBs");
			if (introspectedTable.getRules().generateRecordWithBLOBsClass()) {
	            listType = new FullyQualifiedJavaType(introspectedTable
	                    .getRecordWithBLOBsType());
	        } else {
	            // the blob fields must be rolled up into the base class
	            listType = new FullyQualifiedJavaType(introspectedTable
	                    .getBaseRecordType());
	        }
		} else {
			method.setName(methodName);
			if (introspectedTable.getRules().generateBaseRecordClass()) {
	            listType = new FullyQualifiedJavaType(introspectedTable
	                    .getBaseRecordType());
	        } else if (introspectedTable.getRules().generatePrimaryKeyClass()) {
	            listType = new FullyQualifiedJavaType(introspectedTable
	                    .getPrimaryKeyType());
	        } else {
	            throw new RuntimeException(getString("RuntimeError.12")); //$NON-NLS-1$
	        }
		}
		importedTypes.add(listType);
		returnType.addTypeArgument(listType);
		method.setReturnType(returnType);
		
		method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), parameterName,
				"@Param(\"" + parameterName + "\")"));
		method.addParameter(new Parameter(exampleType, "example", "@Param(\"example\")"));

		this.context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);

		interfaze.addMethod(method);
	}

	/* (non-Javadoc)
	 * @see org.mybatis.generator.api.PluginAdapter#sqlMapDocumentGenerated(org.mybatis.generator.api.dom.xml.Document, org.mybatis.generator.api.IntrospectedTable)
	 */
	public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
		if (!containColumn(introspectedTable)) {
			return true;
		}
		generateXmlDocument(document, introspectedTable, false);
		if (introspectedTable.getRules().generateResultMapWithBLOBs()) {
			generateXmlDocument(document, introspectedTable, true);
		}
		return true;
	}

	public void generateXmlDocument(Document document, IntrospectedTable introspectedTable, boolean withBlobs) {
		XmlElement answer = new XmlElement("select");
		String tmpMethodName = methodName;
		String resultMap = introspectedTable.getBaseResultMapId();
		if (withBlobs) {
			tmpMethodName = methodName + "WithBLOBs";
			resultMap = introspectedTable.getResultMapWithBLOBsId();
		}
		answer.addAttribute(new Attribute("id", tmpMethodName));
		answer.addAttribute(new Attribute("resultMap", resultMap));
		answer.addAttribute(new Attribute("parameterType", "map"));

		this.context.getCommentGenerator().addComment(answer);

		answer.addElement(new TextElement("select"));
		XmlElement ifElement = new XmlElement("if");
		ifElement.addAttribute(new Attribute("test", "example != null and example.distinct"));
		ifElement.addElement(new TextElement("${example.distinct}"));
		answer.addElement(ifElement);
		
		StringBuilder sb = new StringBuilder();
		if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
			sb.append('\'');
			sb.append(introspectedTable.getSelectByExampleQueryId());
			sb.append("' as QUERYID,");
			answer.addElement(new TextElement(sb.toString()));
		}
		
		answer.addElement(getBaseColumnListElement(introspectedTable));
		if (withBlobs) {
			answer.addElement(new TextElement(","));
			answer.addElement(getBlobColumnListElement(introspectedTable));
		}

		sb.setLength(0);
		sb.append("from ");
		sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
		sb.append(" where ");
		answer.addElement(new TextElement(sb.toString()));
		
		// column condition experssion
		answer.addElement(generateColumnConditionElement());
		// example condition experssion
		answer.addElement(generateExampleConditionElement(introspectedTable, withBlobs));

		XmlElement parentElement = document.getRootElement();
		parentElement.addElement(answer);
	}

	private Element generateColumnConditionElement() {
		XmlElement chooseElement = new XmlElement("choose");
		XmlElement whenElement = new XmlElement("when");
		whenElement.addAttribute(new Attribute("test", parameterName + " == null"));
		whenElement.addElement(new TextElement(columnName + " is null"));
		chooseElement.addElement(whenElement);
		whenElement = new XmlElement("when");
		whenElement.addAttribute(new Attribute("test", parameterName + " != null"));
		whenElement.addElement(new TextElement(columnName + " = #{" + parameterName + "}"));
		chooseElement.addElement(whenElement);
		return chooseElement;
	}
	
	private Element generateExampleConditionElement(IntrospectedTable introspectedTable, boolean withBlobs) {
		XmlElement exampleConditionElement = new XmlElement("if");
		exampleConditionElement.addAttribute(new Attribute("test", "example != null"));
		
		XmlElement trimWhereElement = new XmlElement("trim");
		trimWhereElement.addAttribute(new Attribute("prefix", "and ("));
		trimWhereElement.addAttribute(new Attribute("suffix", ")"));
		trimWhereElement.addAttribute(new Attribute("prefixOverrides", "where"));
		trimWhereElement.addElement(getExampleIncludeElement(introspectedTable));
		if (withBlobs) {
			trimWhereElement.addElement(new TextElement(","));
			trimWhereElement.addElement(getBlobColumnListElement(introspectedTable));
		}
		exampleConditionElement.addElement(trimWhereElement);
		
		XmlElement ifOrderElement = new XmlElement("if"); //$NON-NLS-1$
		ifOrderElement.addAttribute(new Attribute("test", "example.orderByClause != null")); //$NON-NLS-1$ //$NON-NLS-2$
		ifOrderElement.addElement(new TextElement("order by ${example.orderByClause}")); //$NON-NLS-1$
		exampleConditionElement.addElement(ifOrderElement);
		return exampleConditionElement;
	}
	
	protected XmlElement getExampleIncludeElement(IntrospectedTable introspectedTable) {
		XmlElement includeElement = new XmlElement("include"); //$NON-NLS-1$
		// 使用update的where构造条件，因为需要example前缀
        includeElement.addAttribute(new Attribute("refid", introspectedTable.getMyBatis3UpdateByExampleWhereClauseId()));
		return includeElement;
	}
	
	protected XmlElement getBaseColumnListElement(IntrospectedTable introspectedTable) {
		XmlElement answer = new XmlElement("include");
		answer.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
		return answer;
	}

	protected XmlElement getBlobColumnListElement(IntrospectedTable introspectedTable) {
		XmlElement answer = new XmlElement("include");
		answer.addAttribute(new Attribute("refid", introspectedTable.getBlobColumnListId()));
		return answer;
	}
	
}